/**
 * 
 */
package edu.byu.nlp.pipes;

import java.util.Iterator;
import java.util.List;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

/**
 * @author robbie
 *
 */
public class Instances {
	
	private Instances() {}
	
	private static abstract class AbstractDataTransformedInstance<L, ID, OD> extends AbstractInstance<L, OD> {

		private final Instance<L, ID> instance;
		
		public AbstractDataTransformedInstance(Instance<L, ID> instance) {
			this.instance = instance;
		}

		@Override
		public L getLabel() {
			return instance.getLabel();
		}

		@Override
		public String getSource() {
			return instance.getSource();
		}

		@Override
		public OD getData() {
			return getData(instance.getData());
		}
		
		public abstract OD getData(ID inputData);
		
	}
	
	public static <L, ID, OD> InstanceFunction<L, ID, L, OD> dataFunction(
			final Function<? super ID, OD> f) {
		
		return new InstanceFunction<L, ID, L, OD>() {

			@Override
			public Instance<L, OD> apply(Instance<L, ID> from) {
				return new AbstractDataTransformedInstance<L, ID, OD>(from) {

					@Override
					public OD getData(ID inputData) {
						return f.apply(inputData);
					}

				};
			}
			
		};
	}
	
	public static <L, ID, OD> Instance<L, OD> transformData(final Instance<L, ID> instance, final Function<ID, OD> f) {
		return Instances.<L, ID, OD>dataFunction(f).apply(instance);
	}
	
	public static <IL, D, OL> InstanceFunction<IL, D, OL, D> labelFunction(
			final Function<? super IL, OL> f) {
		
		return new InstanceFunction<IL, D, OL, D>() {

			@Override
			public Instance<OL, D> apply(final Instance<IL, D> from) {
				return new AbstractInstance<OL, D>() {

					@Override
					public OL getLabel() {
						return f.apply(from.getLabel());
					}

					@Override
					public String getSource() {
						return from.getSource();
					}

					@Override
					public D getData() {
						return from.getData();
					}
					
				};
			}
			
		};
	}
	
	public static <IL, D, OL> Instance<OL, D> transformLabel(final Instance<IL, D> instance, final Function<IL, OL> f) {
		return Instances.<IL, D, OL>labelFunction(f).apply(instance);
	}
	
	public static <L, D> InstanceFunction<L, D, L, D> sourceFunction(
			final Function<String, String> f) {
		
		return new InstanceFunction<L, D, L, D>() {

			@Override
			public Instance<L, D> apply(final Instance<L, D> from) {
				return new AbstractInstance<L, D>() {

					@Override
					public L getLabel() {
						return from.getLabel();
					}

					@Override
					public String getSource() {
						return f.apply(from.getSource());
					}

					@Override
					public D getData() {
						return from.getData();
					}
					
				};
			}
			
		};
	}

	public static <L, D> Instance<L, D> transformSource(final Instance<L, D> instance, final Function<String, String> f) {
		return Instances.<L, D>sourceFunction(f).apply(instance);
	}
	
	public static <L, D> Predicate<Instance<L, D>> labelPredicate(final Predicate<L> predicate) {
		return new Predicate<Instance<L,D>>() {

			@Override
			public boolean apply(Instance<L, D> input) {
				return predicate.apply(input.getLabel());
			}
		};
	}
	
	public static <L, D> boolean filterLabel(Predicate<L> predicate, Instance<L, D> instance) {
		return Instances.<L,D>labelPredicate(predicate).apply(instance);
	}
	
	public static <L, D> Predicate<Instance<L, D>> dataPredicate(final Predicate<D> predicate) {
		return new Predicate<Instance<L,D>>() {

			@Override
			public boolean apply(Instance<L, D> input) {
				return predicate.apply(input.getData());
			}
		};
	}

	public static <L, D> boolean filterData(Predicate<D> predicate, Instance<L, D> instance) {
		return Instances.<L,D>dataPredicate(predicate).apply(instance);
	}
	
	public static <L, D> Predicate<Instance<L, D>> sourcePredicate(
			final Predicate<String> predicate) {

		return new Predicate<Instance<L,D>>() {

			@Override
			public boolean apply(Instance<L, D> input) {
				return predicate.apply(input.getSource());
			}
		};
	}
	
	public static <L, D> boolean filterSource(Predicate<String> predicate, Instance<L, D> instance) {
		return Instances.<L,D>sourcePredicate(predicate).apply(instance);
	}
	
	public static <L, ID, OD> OneToManyInstanceFunction<L, ID, L, OD> oneToManyDataFunction(
			final Function<ID, Iterator<OD>> f) {

		return new OneToManyInstanceFunction<L, ID, L, OD>() {

			@Override
			public Iterator<Instance<L, OD>> apply(final Instance<L, ID> from) {
				Iterator<OD> it = f.apply(from.getData());
				return Iterators.transform(it, new Function<OD, Instance<L, OD>>(){

					@Override
					public Instance<L, OD> apply(final OD data) {
						return new AbstractDataTransformedInstance<L, ID, OD>(from) {

							@Override
							public OD getData(ID inputData) {
								return data;
							}
						};
					}
					
				});
			}
		};
	}

	// TODO(rah67) : move
	public static <T, F> Function<List<T>, List<F>> elementwiseSequenceFunction(final Function<T, F> function) {
		return new Function<List<T>, List<F>>() {
	
			@Override
			public List<F> apply(List<T> from) {
				return Lists.transform(from, function);
			}
		};
	}
	
	public static <L, ID, OD> Iterable<Instance<L, OD>> transformData(Iterable<Instance<L, ID>> it, Function<ID, OD> f) {
		return Pipes.<L, ID, OD>dataTransformingPipe(f).apply(it);
	}

	public static <L, D> Function<Instance<L, D>, D> dataExtractor() {
		return new Function<Instance<L, D>, D>() {

			@Override
			public D apply(Instance<L, D> instance) {
				return instance.getData();
			}
		};
	}

}
